package org.lex.game.sudoku.model;

import java.util.concurrent.atomic.AtomicBoolean;

import ms.sudoku.util.IntSet;

import org.lex.game.sudoku.model.technique.TestTech;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import common.Sudokus;

public class HintCache implements SudokuModelListener {
	private final SudokuModel model;
	private AtomicBoolean hintsAreOutdated = new AtomicBoolean(true);

	private IntSet[][] hints = null;

	private final Logger log = LoggerFactory.getLogger(HintCache.class);

	public HintCache(SudokuModel model) {
		this.model = model;
		this.model.addListener(this);
		int gridSize = model.getGridSize();
		this.hints = new IntSet[gridSize][gridSize];
	}

	@Override
	public void cellChanged(SudokuModel model, Cause cause, int row, int col, int oldValue, int newValue) {
		if (oldValue != newValue) {
			this.hintsAreOutdated.set(true);
		}
	}

	@Override
	public void modelChanged(SudokuModel model) {
		this.hintsAreOutdated.set(true);
	}

	public IntSet getHints(int row, int col) {
		if (true == this.hintsAreOutdated.get()) {
			this.computeAllHints();
			this.hintsAreOutdated.set(false);
		}
		IntSet hint = this.hints[row][col];
		if (null == hint) {
			hint = new IntSet();
			this.hints[row][col] = hint;
		}
		return hint;
	}

	private void computeAllHints() {
		int gridSize = this.model.getGridSize();
		for (int row = 0; row < gridSize; row++) {
			for (int col = 0; col < gridSize; col++) {
				int num = this.model.get(row, col);
				if (Sudokus.notEmpty(num)) {
					// not empty -> no hints
					IntSet hint = this.hints[row][col];
					if (null == hint) {
						hint = new IntSet();
						this.hints[row][col] = hint;
					} else {
						hint.clear();
					}
				} else {
					// is empty -> re-compute cell hints
					this.hints[row][col] = this.computeCellHints(row, col);
				}
			}
		}
	}

	/**
	 * 计算单元格的所有提示数字
	 * 
	 * @param row
	 * @param column
	 * @return
	 */
	private IntSet computeCellHints(int row, int column) {
		IntSet results = new IntSet();

		// not an empty cell, so no hints
		if (Sudokus.notEmpty(this.model.get(row, column))) {
			return results;
		}

		for (int num = 1, max = model.getGridSize(); num <= max; num++) {
			boolean canFill = this.model.checkCanFillWith(row, column, num);
			if (true == canFill) {
				results.add(num);
			}
		}
		if (1 != results.size()) {
			IntSet copy = results.clone();
			results = new TestTech().test(model, row, column, results);
			if (1 == results.size()) {
				log.debug("TestTech works on cell(" + row + ", " + column + "): eliminate from " + copy + " to "
						+ results);
			}
		}
		return results;
	}
}
